#include "include/monitoring-module.h"

int pid = 1;
module_param(pid, int, 0);
MODULE_PARM_DESC(pid, "Process ID to monitor (default 1)");

// Informació del módul
MODULE_AUTHOR("Alex Alvarez Chinchilla & Carlos Garcia Marquez");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("TODO");

/*
 * Inicialització
 */

static int __init Monitoring_init(void)
{
	default_sys_calls[OPEN]  = sys_call_table[POS_SYSCALL_OPEN];
	default_sys_calls[WRITE] = sys_call_table[POS_SYSCALL_WRITE];
	default_sys_calls[CLONE] = sys_call_table[POS_SYSCALL_CLONE];
	default_sys_calls[CLOSE] = sys_call_table[POS_SYSCALL_CLOSE];
	default_sys_calls[LSEEK] = sys_call_table[POS_SYSCALL_LSEEK];

	monitor_sys_calls[OPEN]  = sys_open_monitor;
	monitor_sys_calls[WRITE] = sys_write_monitor;
	monitor_sys_calls[CLONE] = sys_clone_monitor;
	monitor_sys_calls[CLOSE] = sys_close_monitor;
	monitor_sys_calls[LSEEK] = sys_lseek_monitor;

	enable_monitoring(NR_CALLS);
	return 0;
}

/*
 * Descarrega del módul
 */

static void __exit Monitoring_exit(void)
{
	disable_monitoring(NR_CALLS);
	print_stats();
	return;
}

/*
 * Gestió interna del módul
 */

EXPORT_SYMBOL(reset_process_statistics);
void reset_process_statistics(struct statistics *stats)
{
	int i;
	for (i = 0; i < NR_CALLS; i++) {
		stats[i].calls = 0;
		stats[i].success = 0;
		stats[i].error = 0;
		stats[i].total_time = 0;
	}
}

EXPORT_SYMBOL(reset_all_statistics);
void reset_all_statistics()
{
	struct task_struct *task;
	for_each_process(task) {
		reset_process_statistics(((struct my_thread_info *)task->thread_info)->stats);
	}
}

EXPORT_SYMBOL(enable_monitoring);
int enable_monitoring(int call)
{
	if (call < 0 || call > NR_CALLS) {
		return -EINVAL;
	} else if (call == NR_CALLS) {
		sys_call_table[POS_SYSCALL_OPEN]  = monitor_sys_calls[OPEN];
		sys_call_table[POS_SYSCALL_WRITE] = monitor_sys_calls[WRITE];
		sys_call_table[POS_SYSCALL_CLONE] = monitor_sys_calls[CLONE];
		sys_call_table[POS_SYSCALL_CLOSE] = monitor_sys_calls[CLOSE];
		sys_call_table[POS_SYSCALL_LSEEK] = monitor_sys_calls[LSEEK];
	} else {
		sys_call_table[call_positions[call]] = monitor_sys_calls[call];
	}
	return 0;
}

EXPORT_SYMBOL(disable_monitoring);
int disable_monitoring(int call)
{
	if (call < 0 || call > NR_CALLS) {
		return -EINVAL;
	} else if (call == NR_CALLS) {
		sys_call_table[POS_SYSCALL_OPEN]  = default_sys_calls[OPEN];
		sys_call_table[POS_SYSCALL_WRITE] = default_sys_calls[WRITE];
		sys_call_table[POS_SYSCALL_CLONE] = default_sys_calls[CLONE];
		sys_call_table[POS_SYSCALL_CLOSE] = default_sys_calls[CLOSE];
		sys_call_table[POS_SYSCALL_LSEEK] = default_sys_calls[LSEEK];
	} else {
		sys_call_table[call_positions[call]] = default_sys_calls[call];
	}
	return 0;
}

void print_stats(void)
{
	struct task_struct *ts;
	struct my_thread_info *ti;
	struct statistics *st;
	int i;
	ts = find_task_by_pid(pid);
	if (ts == 0) {
		printk(KERN_ERR "The process does not exist anymore\n");
		return;
	}
	ti = (struct my_thread_info*) ts->thread_info;
	st = ti->stats;
	printk(KERN_INFO "pid: %d\n", pid);
	for (i = 0; i < NR_CALLS; i++) {
		printk(KERN_INFO "%s\ttotal: %d, successful: %d, incorrect: %d, time: %llu\n",
				call_names[i], st[i].calls, st[i].success, st[i].error, st[i].total_time);
	}
	//FIXME
/*	for_each_process(ts) {
		printk(KERN_INFO "PID: %d\n", ((struct my_thread_info *)ts->thread_info)->pid);
		printk(KERN_INFO "Total calls: %d\n", ((struct my_thread_info *)ts->thread_info)->stats[0].calls);
		printk(KERN_INFO "Successful calls: %d\n", ((struct my_thread_info *)ts->thread_info)->stats[0].success);
		printk(KERN_INFO "Incorrect calls: %d\n", ((struct my_thread_info *)ts->thread_info)->stats[0].error); 
		printk(KERN_INFO "Total time: %lld\n", ((struct my_thread_info *)ts->thread_info)->stats[0].total_time);
	}//*/
}

// Funció que mesura temps
#define proso_rdtsc(low,high) \
__asm__ __volatile__("rdtsc" : "=a" (low), "=d" (high))
static inline unsigned long long proso_get_cycles (void) {
	unsigned long eax, edx;
	proso_rdtsc(eax, edx);
	return ((unsigned long long) edx << 32) + eax;
}

/*
 * Funcions de monitorització
 */

long sys_open_monitor(const char __user * filename, int flags, int mode)
{
	struct my_thread_info *ti;
	long result;
	unsigned long long cycles;
	
	try_module_get(THIS_MODULE);

	ti = (struct my_thread_info*) current_thread_info();
	if (ti->pid != current->pid) {
		reset_process_statistics(ti->stats);
		ti->pid = current->pid;
	}

	ti->stats[OPEN].calls++;
	cycles = proso_get_cycles();
	result = ((long(*)(const char __user *, int, int))(default_sys_calls[OPEN]))(filename, flags, mode);
	ti->stats[OPEN].total_time += proso_get_cycles() - cycles;
	if (result >= 0) {
		ti->stats[OPEN].success++;
	} else {
		ti->stats[OPEN].error++;
	}

	module_put(THIS_MODULE);

	return result;
}

long sys_close_monitor(unsigned int fd)
{
	struct my_thread_info *ti;
	long result;
	unsigned long long cycles;
	
	try_module_get(THIS_MODULE);

	ti = (struct my_thread_info*) current_thread_info();
	if (ti->pid != current->pid) {
		reset_process_statistics(ti->stats);
		ti->pid = current->pid;
	}

	ti->stats[CLOSE].calls++;
	cycles = proso_get_cycles();
	result = ((long(*)(int))(default_sys_calls[CLOSE]))(fd);
	ti->stats[CLOSE].total_time += proso_get_cycles() - cycles;
	if (result >= 0) {
		ti->stats[CLOSE].success++;
	} else {
		ti->stats[CLOSE].error++;
	}

	module_put(THIS_MODULE);

	return result;
}

ssize_t sys_write_monitor(unsigned int fd, const char __user * buf, size_t count)
{
	struct my_thread_info *ti;
	ssize_t result;
	unsigned long long cycles;
	
	try_module_get(THIS_MODULE);

	ti = (struct my_thread_info*) current_thread_info();
	if (ti->pid != current->pid) {
		reset_process_statistics(ti->stats);
		ti->pid = current->pid;
	}

	ti->stats[WRITE].calls++;
	cycles = proso_get_cycles();
	result = ((ssize_t(*)(unsigned int, const char __user *, size_t))(default_sys_calls[WRITE]))(fd, buf, count);
	ti->stats[WRITE].total_time += proso_get_cycles() - cycles;
	if (result >= 0) {
		ti->stats[WRITE].success++;
	} else {
		ti->stats[WRITE].error++;
	}

	module_put(THIS_MODULE);

	return result;
}

off_t sys_lseek_monitor(unsigned int fd, off_t offset, unsigned int origin)
{
	struct my_thread_info *ti;
	off_t result;
	unsigned long long cycles;
	
	try_module_get(THIS_MODULE);

	ti = (struct my_thread_info*) current_thread_info();
	if (ti->pid != current->pid) {
		reset_process_statistics(ti->stats);
		ti->pid = current->pid;
	}
	
	ti->stats[LSEEK].calls++;
	cycles = proso_get_cycles();
	result = ((off_t (*)(unsigned int, off_t, unsigned int))(default_sys_calls[LSEEK]))(fd, offset, origin);
	ti->stats[LSEEK].total_time += proso_get_cycles() - cycles;
	if (result >= 0) {
		ti->stats[LSEEK].success++;	
	} else {
		ti->stats[LSEEK].error++;
	}

	module_put(THIS_MODULE);

	return result;
}

int sys_clone_monitor(struct pt_regs regs)
{
	struct my_thread_info *ti;
	int result;
	unsigned long long cycles;
	
	try_module_get(THIS_MODULE);

	ti = (struct my_thread_info*) current_thread_info();
	if (ti->pid != current->pid) {
		reset_process_statistics(ti->stats);
		ti->pid = current->pid;
	}

	ti->stats[CLONE].calls++;
	cycles = proso_get_cycles();
	result = ((int(*)(struct pt_regs))(default_sys_calls[CLONE]))(regs);
	ti->stats[CLONE].total_time += proso_get_cycles() - cycles;
	if (result >= 0) {
		ti->stats[CLONE].success++;
	} else {
		ti->stats[CLONE].error++;
	}

	module_put(THIS_MODULE);

	return result;
}

module_init(Monitoring_init);
module_exit(Monitoring_exit);
